package top.z.template;

import top.z.template.parse.Span;
import top.z.template.parse.TokenStream;

/**
 * 错误处理类
 */
public class Error {

    /**
     * 根据提供的消息和流创建错误消息
     */
    public static TemplateException error(String message, TokenStream stream) {
        if (stream.hasNext()) {
            return error(message, stream.consume().getSpan());
        } else {
            Source source = stream.getSource();
            if (source == null) {
                return error(message, new Span(new Source("unknown", " "), 0, 1));
            } else {
                return error(message, new Span(source, source.getContent().length() - 1, source.getContent().length()));
            }
        }
    }

    /**
     * 根据提供的消息和位置创建错误消息, 突出显示发生错误的行中的位置
     */
    public static TemplateException error(String message, Span location, Throwable cause) {
        Span.Line line = location.getLine();
        message = "Error (" + location.getSource().getPath() + ":" + line.getLine() + "): " + message + "\n";
        message += line.getText();
        message += "\n";

        int           errorStart     = location.getStart() - line.getStart();
        int           errorEnd       = errorStart + location.getText().length() - 1;
        StringBuilder messageBuilder = new StringBuilder(message);
        for (int i = 0, n = line.getText().length(); i < n; i++) {
            boolean useTab = line.getText().charAt(i) == '\t';
            messageBuilder.append(i >= errorStart && i <= errorEnd ? "^" : useTab ? "\t" : " ");
        }
        message = messageBuilder.toString();

        if (cause == null) {
            return new TemplateException(message, location);
        } else {
            return new TemplateException(message, location, cause);
        }
    }

    /**
     * 根据提供的消息和位置创建错误消息
     */
    public static TemplateException error(String message, Span location) {
        return error(message, location, null);
    }

    public static class TemplateException extends RuntimeException {

        private static final long serialVersionUID = 1L;

        private final Span   location;
        private final String errorMessage;

        private TemplateException(String message, Span location) {
            super(message);
            this.errorMessage = message;
            this.location = location;
        }

        public TemplateException(String message, Span location, Throwable cause) {
            super(message, cause);
            this.errorMessage = message;
            this.location = location;
        }

        /**
         * 返回模板中发生错误的位置
         */
        public Span getLocation() {
            return location;
        }

        @Override
        public String getMessage() {
            StringBuilder builder = new StringBuilder();

            if (getCause() == null || getCause() == this) {
                return super.getMessage();
            }

            builder.append(errorMessage, 0, errorMessage.indexOf('\n'));
            builder.append("\n");

            Throwable cause = getCause();
            while (cause != null && cause != this) {
                if (cause instanceof TemplateException) {
                    TemplateException ex = (TemplateException) cause;
                    if (ex.getCause() == null || ex.getCause() == ex)
                        builder.append(ex.errorMessage);
                    else
                        builder.append(ex.errorMessage, 0, ex.errorMessage.indexOf('\n'));
                    builder.append("\n");
                }
                cause = cause.getCause();
            }
            return builder.toString();
        }
    }
}
